/*
 * CMediaCaster.cpp
 *
 *  Created on: 2016-6-21
 *      Author: terry
 */

#include "CMediaCaster.h"
#include "H264NaluParser.h"
#include "CLog.h"


namespace av
{


CMediaCaster::CMediaCaster():
		m_videoLastPts(),
		m_audioLastPts(),
		m_videoClock(1000000),
		m_audioClock(1000000)
{
}

CMediaCaster::~CMediaCaster()
{
}

int CMediaCaster::open(const std::string& url, const std::string& params)
{
	m_url = url;

	return 0;
}

void CMediaCaster::close()
{
	m_url.clear();
}

bool CMediaCaster::isOpen()
{
	return (!m_url.empty());
}

const char* CMediaCaster::getName()
{
    return m_url.c_str();
}

bool CMediaCaster::getFormat(MediaFormat& fmt)
{
	fmt = m_format;
	return true;
}

bool CMediaCaster::setFormat(const MediaFormat& fmt)
{
	m_videoClock = fmt.m_clockRate > 0 ? fmt.m_clockRate : 1000000;
	m_audioClock = fmt.m_audioRate > 0 ? fmt.m_audioRate : 1000000;

	m_format = fmt;

	m_format.m_clockRate = 1000000;
	m_format.m_audioRate = 1000000;

	return true;
}

bool CMediaCaster::write(int type, uint8_t* data, int size, int64_t pts, int duration, int flags)
{
	if (!data || size <= 0)
	{
		return false;
	}

	if (type == MEDIA_TYPE_VIDEO)
	{
		if (m_videoClock != m_format.m_clockRate)
		{
			pts = pts * m_format.m_clockRate / m_videoClock;
		}
	}
	else if (type == MEDIA_TYPE_AUDIO)
	{
		if (m_audioClock != m_format.m_audioRate)
		{
			pts = pts * m_format.m_audioRate / m_audioClock;
		}
	}

    MediaPacketPtr pkt(new MediaPacket());
    pkt->type = type;
    pkt->pts = pts;
    pkt->duration = duration;
    pkt->flags = flags;
    pkt->set(data, size);

	correctDuration(*pkt.get());

	//if (pkt->isVideo())
	//{
	//	CLog::debug("type:%d, size:%d, pts:%I64d, duration:%d\n", type, size, pts, pkt->duration);
	//}

	if (pkt->isVideo() && m_format.m_videoProp.empty())
	{
		parseParamSet(*pkt.get());
	}

	fireMediaPacket(pkt);

	return true;
}


void CMediaCaster::parseParamSet(MediaPacket& pkt)
{
    if (m_format.m_codec == MEDIA_CODEC_H264)
    {
        parseH264ParamSet(pkt);
    }
    else if (m_format.m_codec == MEDIA_CODEC_HEVC)
    {
        parseH265ParamSet(pkt);
    }
}

bool CMediaCaster::parseH264ParamSet(MediaPacket& pkt)
{
    if (!H264NaluParser::startWithH264Code(pkt.data, pkt.size))
    {
        return false;
    }

    uint8_t* data = pkt.data;
    size_t size = pkt.size;

    size_t begin = 0;
    bool found = true;

    while (found)
    {
        size_t pos = H264NaluParser::findH264StartCode(data, size, begin + 3);
        if (pos != -1)
        {
            parseH264Nalu(data + begin, pos - begin);
            begin = pos;
        }
        else
        {
            break;
        }
    }

    parseH264Nalu(data + begin, size - begin);

    if (m_sps.size() && m_pps.size())
    {
        std::string prop;
        prop.append((char*)H264NaluParser::s_startCode, H264NaluParser::START_CODE_LENGTH);
        prop.append(m_sps);
        prop.append((char*)H264NaluParser::s_startCode, H264NaluParser::START_CODE_LENGTH);
        prop.append(m_pps);

        m_format.setVideoProp(prop.c_str(), prop.size());

        return true;
    }

    return false;
}

bool CMediaCaster::parseH265ParamSet(MediaPacket& pkt)
{
    if (!H264NaluParser::startWithH264Code(pkt.data, pkt.size))
    {
        return false;
    }

    uint8_t* data = pkt.data;
    size_t size = pkt.size;

    size_t begin = 0;
    bool found = true;

    while (found)
    {
        size_t pos = H264NaluParser::findH264StartCode(data, size, begin + 3);
        if (pos != -1)
        {
            parseH265Nalu(data + begin, pos - begin);
            begin = pos;
        }
        else
        {
            break;
        }
    }

    parseH265Nalu(data + begin, size - begin);

    if (m_sps.size() && m_pps.size())
    {
        std::string prop;
        prop.append((char*)H264NaluParser::s_startCode, H264NaluParser::START_CODE_LENGTH);
        prop.append(m_vps);
        prop.append((char*)H264NaluParser::s_startCode, H264NaluParser::START_CODE_LENGTH);
        prop.append(m_sps);
        prop.append((char*)H264NaluParser::s_startCode, H264NaluParser::START_CODE_LENGTH);
        prop.append(m_pps);

        m_format.setVideoProp(prop.c_str(), prop.size());

        return true;
    }

    return false;
}

void CMediaCaster::parseH264Nalu(const uint8_t* data, size_t length)
{
    NaluPacket nalu;
    if (!H264NaluParser::parseNalu(data, length, nalu))
    {
        return ;
    }

    if (nalu.type == NaluPacket::NALU_SPS)
    {
        m_sps.assign((char*)nalu.data, nalu.length);
    }
    else if (nalu.type == NaluPacket::NALU_PPS)
    {
        m_pps.assign((char*)nalu.data, nalu.length);
    }
}

void CMediaCaster::parseH265Nalu(const uint8_t* data, size_t length)
{
    NaluPacket nalu;
    if (!H264NaluParser::parseNalu(data, length, nalu))
    {
        return ;
    }

    nalu.type = (nalu.data[0] & 0x7E) >> 1;
    if (nalu.type == 32)
    {
        m_vps.assign((char*)nalu.data, nalu.length);
    }
    else if (nalu.type == 33)
    {
        m_sps.assign((char*)nalu.data, nalu.length);
    }
    else if (nalu.type == 34)
    {
        m_pps.assign((char*)nalu.data, nalu.length);
    }
}

void CMediaCaster::correctDuration(MediaPacket& pkt)
{
    if (pkt.isVideo())
    {
        if (m_videoLastPts == 0)
        {
            m_videoLastPts = pkt.pts;
        }

        if (pkt.duration <= 0)
        {
            pkt.duration = (int)(pkt.pts - m_videoLastPts);
        }

        m_videoLastPts = pkt.pts;
    }
    else
    {
        if (m_audioLastPts == 0)
        {
            m_audioLastPts = pkt.pts;
        }

        if (pkt.duration <= 0)
        {
            pkt.duration = (int)(pkt.pts - m_audioLastPts);
        }

        m_audioLastPts = pkt.pts;
    }

}

} /* namespace av */
